import struct
import csv
import os
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import datetime


def convert_to_csv(input_path, output_path, progress_var, status_var):
    try:
        # 验证文件大小是否为17字节的倍数
        file_size = os.path.getsize(input_path)
        if file_size % 17 != 0:
            messagebox.showerror("格式错误", f"文件大小不匹配: {file_size}字节\n"
                                             "必须是17字节(CAN_LogEntry大小)的倍数")
            return False

        struct_format = '<IIB8B'  # 小端字节序
        entry_size = struct.calcsize(struct_format)
        total_entries = file_size // entry_size
        processed = 0

        with open(input_path, 'rb') as bin_file, open(output_path, 'w', newline='') as csv_file:
            csv_writer = csv.writer(csv_file)
            # 写入CSV表头
            csv_writer.writerow(
                ['序号', '时间标识', '传输方向', '帧类型', '帧格式', '帧ID', '数据长度', '帧数据', '时间戳'])

            index = 1
            while True:
                chunk = bin_file.read(entry_size)
                if not chunk:
                    break

                # 解析二进制数据
                timestamp, can_id, dlc, *data = struct.unpack(struct_format, chunk)

                # 时间格式化 (秒.毫秒.微秒)
                total_seconds = timestamp / 1000.0  # 转换为秒（浮点数）
                seconds = int(total_seconds)  # 获取整数秒部分
                fractional = total_seconds - seconds  # 获取小数部分

                milliseconds = int(fractional * 1000)  # 转换为毫秒(0-999)
                microseconds = int((fractional * 1000000) % 1000)  # 获取微秒部分(0-999)

                time_str = f"{seconds}.{milliseconds:03d}.{microseconds:03d}"

                # 帧类型判断
                frame_type = "标准帧" if can_id <= 0x7FF else "扩展帧"

                # 帧ID格式化 (带0x前缀)
                frame_id = f"0x{can_id:04X}" if frame_type == "标准帧" else f"0x{can_id:08X}"

                # 数据域格式化 (HEX空格分隔)
                hex_data = ' '.join(f"{b:02X}" for b in data[:dlc])
                timestamp = timestamp * 1000
                # 写入CSV行
                csv_writer.writerow([
                    index,
                    time_str,
                    "发送",
                    frame_type,
                    "数据",
                    frame_id,
                    dlc,
                    hex_data,
                    timestamp
                ])
                index += 1
                processed += 1

                # 更新进度
                if processed % 100 == 0 or processed == total_entries:
                    progress = int((processed / total_entries) * 100)
                    progress_var.set(progress)
                    status_var.set(f"处理中: {processed}/{total_entries} 条记录")
                    progress_var._root.update_idletasks()  # 强制UI更新

        return True

    except Exception as e:
        messagebox.showerror("转换错误", f"发生错误: {str(e)}")
        return False


def select_input_file(entry):
    file_path = filedialog.askopenfilename(
        title="选择CAN日志文件",
        filetypes=[("CAN日志文件", "*.dat"), ("所有文件", "*.*")]
    )
    if file_path:
        entry.delete(0, tk.END)
        entry.insert(0, file_path)


def select_output_file(entry):
    default_name = "converted_output.csv"
    file_path = filedialog.asksaveasfilename(
        title="保存CSV文件",
        filetypes=[("CSV文件", "*.csv"), ("所有文件", "*.*")],
        defaultextension=".csv",
        initialfile=default_name
    )
    if file_path:
        entry.delete(0, tk.END)
        entry.insert(0, file_path)


def start_conversion():
    input_path = input_entry.get()
    output_path = output_entry.get()

    if not input_path:
        messagebox.showwarning("输入错误", "请选择输入文件")
        return
    if not output_path:
        messagebox.showwarning("输入错误", "请指定输出文件")
        return

    # 重置进度
    progress_var.set(0)
    status_var.set("开始转换...")
    root.update()  # 立即更新UI

    # 执行转换
    success = convert_to_csv(input_path, output_path, progress_var, status_var)

    if success:
        status_var.set("转换完成!")
        messagebox.showinfo("成功", f"文件转换完成\n保存至: {output_path}")


# 创建主窗口
root = tk.Tk()
root.title("CAN数据转换工具")
root.geometry("800x600")
root.resizable(True, True)

# 使用主题美化
style = ttk.Style()
style.theme_use("clam")

# 创建框架
main_frame = ttk.Frame(root, padding="20")
main_frame.pack(fill=tk.BOTH, expand=True)

# 输入文件选择
input_frame = ttk.Frame(main_frame)
input_frame.pack(fill=tk.X, pady=10)

ttk.Label(input_frame, text="输入文件(.dat):").pack(side=tk.LEFT)
input_entry = ttk.Entry(input_frame, width=50)
input_entry.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True)
ttk.Button(input_frame, text="浏览...",
           command=lambda: select_input_file(input_entry)).pack(side=tk.LEFT)

# 输出文件选择
output_frame = ttk.Frame(main_frame)
output_frame.pack(fill=tk.X, pady=10)

ttk.Label(output_frame, text="输出文件(.csv):").pack(side=tk.LEFT)
output_entry = ttk.Entry(output_frame, width=50)
output_entry.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True)
ttk.Button(output_frame, text="浏览...",
           command=lambda: select_output_file(output_entry)).pack(side=tk.LEFT)

# 添加说明
info_frame = ttk.LabelFrame(main_frame, text="转换说明", padding=10)
info_frame.pack(fill=tk.X, pady=10)

info_text = """1. 输入文件必须是二进制CAN日志(.dat)
2. 输出格式: 带0x前缀的16进制帧ID
3. 时间戳转换为: 秒.毫秒.微秒格式
4. 标准帧显示为0xXXXX格式，扩展帧为0xXXXXXXXX
5. 时间标识格式: 秒.毫秒.微秒 (如 123.456.789)"""

ttk.Label(info_frame, text=info_text, justify=tk.LEFT).pack(anchor=tk.W)

# 进度条区域
progress_frame = ttk.Frame(main_frame)
progress_frame.pack(fill=tk.X, pady=20)

progress_var = tk.IntVar()
progress_bar = ttk.Progressbar(progress_frame, variable=progress_var, maximum=100)
progress_bar.pack(fill=tk.X)

status_var = tk.StringVar(value="准备就绪")
status_label = ttk.Label(progress_frame, textvariable=status_var)
status_label.pack(pady=5)

# 开始按钮
button_frame = ttk.Frame(main_frame)
button_frame.pack(pady=10)
start_button = ttk.Button(button_frame, text="开始转换",
                          command=start_conversion, style='Accent.TButton')
start_button.pack(side=tk.LEFT, padx=5)

# 退出按钮
exit_button = ttk.Button(button_frame, text="退出",
                         command=root.destroy)
exit_button.pack(side=tk.LEFT, padx=5)

# 添加一个自定义主题按钮样式
style.configure('Accent.TButton', font=('Arial', 10, 'bold'), foreground='white', background='#4CAF50')
style.map('Accent.TButton',
          foreground=[('pressed', 'white'), ('active', 'white')],
          background=[('pressed', '#388E3C'), ('active', '#66BB6A')])

# 版权信息
copyright_frame = ttk.Frame(main_frame)
copyright_frame.pack(side=tk.BOTTOM, fill=tk.X, pady=5)
copyright_label = ttk.Label(copyright_frame, text="© 2025 CAN数据转换工具", foreground="gray")
copyright_label.pack()

# 运行主循环
root.mainloop()